import $ from 'jquery'
import _ from 'lodash'
import Vue from 'vue'

var db = {
    loadDirectory: function() {
        return $.post('/model/directory/read').then(res => {
            return $.Deferred(w => {
                w.resolve([res.root].concat(res.dirs));
            })
        })
        .then(function(docs) {
            db.data.directory = Object.defineProperty({}, '$Root', {
                value: docs[0]
            });
            db.data.directory.__proto__ = db.handler.directory;
            docs.forEach(function(doc, index) {
                if (!index) {
                    return;
                }
                db.data.directory[doc._id] = doc;
            });
        });
    },
    loadTag: function() {
        return $.post('/model/tag/read').then(res => {
            return $.Deferred(w => {
                w.resolve(res.tags);
            })
        }).then(function(docs) {
            db.data.tag = {};
            db.data.tag.__proto__ = db.handler.tag;
            docs.forEach(function(doc) {
                db.data.tag[doc._id] = doc;
            });
        });
    },
    loadImage: function() {
        return $.post('/model/image/read').then(res => {
            return $.Deferred(w => w.resolve(res.images));
        })
        .then(function(docs) {
            db.data.image = {};
            db.data.image.__proto__ = db.handler.image;
            docs.forEach(function(doc) {
                db.data.image[doc._id] = doc;
            });
            return $.Deferred(w => setTimeout(w.resolve, 
                Math.floor(Math.random() * 1000) ) );
        });
    },
    load() {
        var $loading = $.Deferred();
        $.when(
            db.loadDirectory().done(function() {
                 $loading.notify('directory');
            }),
            db.loadTag().done(function() {
                $loading.notify('tag');
            }),
            db.loadImage().done(function() {
                $loading.notify('image');
            })
        ).then(function() {
            db.initReverseMap();
            setTimeout($loading.resolve, 100, db);
        })
        return $loading.promise();
    },
    initReverseMap() {
        var reverseMap = db.reverseMap = {};
        Object.keys(db.image).forEach(_id => {
            db.createMapItem(db.image[_id]);
        });

        Object.keys(db.directory).forEach(_id => {
            db.registerInReverseMap('directory', db.directory[_id]);
        });

        Object.keys(db.tag).forEach(_id => {
            db.registerInReverseMap('tag', db.tag[_id]);
        });

        for (var _id in reverseMap) {
            var obj = reverseMap[_id];
            if (obj.loss) {
                console.warn('image has no reference: ', _id, db.image[_id]);
            }
        }
    },
    createMapItem(image) {
        var reverseMap = db.reverseMap;
        if (image._id in reverseMap) {
            console.warn('redefinded in reverseMap? target is:', image, '\n\tignored');
            return;
        }
        reverseMap[image._id] = {
            directory: Object.defineProperty({}, '__counter', {
               value: 0,
               writable: true
            }),
            tag: Object.defineProperty({}, '__counter', {
               value: 0,
               writable: true
            }),
            loss: true
        };
    },
    registerInReverseMap(type, obj) {
        var reverseMap = db.reverseMap;
        obj.content.forEach(_id => {
            if (!(_id in reverseMap)) {
                if (!db.image.hasOwnProperty(_id)) {
                    console.warn('unfound image id:', _id, `\n\tin ${type}:`, obj);    
                } else {
                    db.createMapItem(db.image[_id]);
                }
            }
            var target = reverseMap[_id][type];
            !target[obj._id] && target.__counter++;
            reverseMap[_id].loss = !target.__counter;
            target[obj._id] = true;
        });
    },
    unRegisterInReverseMap(type, obj) {
        var reverseMap = db.reverseMap;
        obj.content.forEach(_id => {
            if (!(_id in reverseMap)) {
                if (!db.image.hasOwnProperty(_id)) {
                    console.warn('unfound image id:', _id, `\n\tin ${type}:`, obj);
                } else {
                    console.warn('uncreate image id:', _id, `\n\tin ${type}:`, obj);
                    db.createMapItem(db.image[_id]);
                }
            }
            var target = reverseMap[_id][type];
            target[obj._id] && target.__counter--;
            reverseMap[_id].loss = !target.__counter;
            delete target[obj._id];
        });
    },
    update(target, result) {
        db._buffer.push([target, result]);
        if (db.autoFlush) {
            db.flush();
        }
    },
    _buffer: [],
    _update(target, result) {
        var name = result.name,
            obj = result.obj;
        
        var prev = target[obj._id];

        switch(name) {
            case "create":
                Vue.set(target, obj._id, obj);
                target.onCreate && target.onCreate(obj);
                break;
            case "update":
                Vue.set(target, obj._id, obj);
                prev || (target.onCreate && target.onCreate(obj));
                target.onUpdate && target.onUpdate(obj, prev);
                break;
            case "delete":
                Vue.delete(target, obj._id);
                target.onDelete && target.onDelete(obj, prev);
                break;
            case "merge":
                var hold = Object.assign({}, prev);
                for (var i in prev) {
                    Vue.set(prev, i, obj[i]);
                }
                target.onMerge && target.onMerge(obj, hold, prev);
                break;
            default:
                throw { type: 'unknown name', name, obj, result};
                break;
        }
    },
    flush() {
        db._buffer.forEach(obj => {
            db._update.apply(db, obj);
        });
        db._buffer.splice(0, db._buffer.length);
        db.data._oid++;
    },
    autoFlush: true,
    ajax: {
        _c: 0,
        _uid: function() {
            return Math.random().toString(16).slice(2, 8) + (++this._c);
        },
        createTag(name) {
            if (!name) {
                return $.Deferred(w => w.reject(null));
            }
            return $.post('/model/tag/create', {
                args: JSON.stringify({name})
            }).then(res => {
                return db.ajax.defaultResCreator(res.self, 'create');
            })
            .then(db.ajax.defaultHandler.bind(db.ajax, db.tag))
        },
        deleteTag(_id) {
            if (!db.tag.hasOwnProperty(_id)) {
                return $.Deferred(w => w.reject(null));
            }
            return $.post('/model/tag/delete', {
                _id
            }).then(res => {
                return db.ajax.defaultResCreator(res.self, 'delete');
            })
            .then(db.ajax.defaultHandler.bind(db.ajax, db.tag))
        },
        createDirectory(parent_id, name) {
            if (!parent_id || !(db.directory.hasOwnProperty(parent_id)) || !name) {
                return $.Deferred(w => w.reject(null));
            }
            return $.post('/model/directory/create', {
                args: JSON.stringify({
                    parent: parent_id,
                    name: name
                })
            })
            .then(res => {
                return $.when(
                    db.ajax.defaultResCreator(res.self, 'create'),
                    db.ajax.defaultResCreator(res.parent, 'merge')
                ).then(function([rs1], [rs2]) {
                    rs1.result = rs1.result.concat(rs2.result);
                    return $.Deferred(w => w.resolve(rs1, res.self));
                })
            })
            .then(db.ajax.defaultHandler.bind(db.ajax, db.directory))
        },
        removeDirectoryFromParent(parent_id, _id) {
            if (!db.directory.hasOwnProperty(_id) || !db.directory.hasOwnProperty(parent_id)) {
                return $.Deferred(w => w.reject(null));
            }
            return $.post('/model/directory/removeNext', {
                _id: parent_id,
                args: JSON.stringify({
                    child_id: [_id]
                })
            })
            .then(res => {
                return db.ajax.defaultResCreator(res.self, 'merge');
            })
            .then(db.ajax.defaultHandler.bind(db.ajax, db.directory));
        },
        deleteDirectory(_id) {
            if (!db.directory.hasOwnProperty(_id)) {
                return $.Deferred(w => w.reject(null));
            }
            throw "undefined methods";
        },
        addImagesToTag([..._imageIds], _id) {
            if (!db.tag.hasOwnProperty(_id)) {
                return $.Deferred(w => w.reject(null));
            }
            return $.post('/model/tag/addImages', {
                _id,
                args: JSON.stringify({
                    child_id: _imageIds
                })
            })
            .then(res => {
                return db.ajax.defaultResCreator(res.self, 'merge');
            })
            .then(db.ajax.defaultHandler.bind(db.ajax, db.tag));
        },
        removeImagesFromTag([..._imageIds], _id) {
            if (!db.tag.hasOwnProperty(_id)) {
                return $.Deferred(w => w.reject(null));
            }
            return $.post('/model/tag/removeImages', {
                _id,
                args: JSON.stringify({
                    child_id: _imageIds
                })
            })
            .then(res => {
                return db.ajax.defaultResCreator(res.self, 'merge');
            })
            .then(db.ajax.defaultHandler.bind(db.ajax, db.tag));
        },
        addImagesToDirectory([..._imageIds], _id) {
            if (!db.directory.hasOwnProperty(_id)) {
                return $.Deferred(w => w.reject(null));
            }
            return $.post('/model/directory/addImages', {
                _id,
                args: JSON.stringify({
                    child_id: _imageIds
                })
            })
            .then(res => {
                return db.ajax.defaultResCreator(res.self, 'merge');
            })
            .then(db.ajax.defaultHandler.bind(db.ajax, db.directory));
        },
        removeImagesFromDirectory([..._imageIds], _id) {
            if (!db.directory.hasOwnProperty(_id)) {
                return $.Deferred(w => w.reject(null));
            }
            return $.post('/model/directory/removeImages', {
                _id,
                args: JSON.stringify({
                    
                    child_id: _imageIds
                })
            })
            .then(res => {
                return db.ajax.defaultResCreator(res.self, 'merge');
            }).then(db.ajax.defaultHandler.bind(db.ajax, db.directory));
        },
        defaultResCreator(entity, name) {
            var opr = {
                result: [
                    {
                        name,
                        obj: entity
                    }
                ]
            };
            return $.Deferred(w => w.resolve(opr, entity));
        },
        defaultHandler(target, res, entity) {
            res.result.forEach(x => {
                db.update(target, x);
            });
            return $.Deferred(w => w.resolve(entity));
        }

    },
    handler: {
        directory: _handler({
            onMerge: function(obj, prev) {
                db.unRegisterInReverseMap('directory', prev);
                db.registerInReverseMap('directory', obj);
            },
            onCreate: function(obj) {
                db.registerInReverseMap('directory', obj);
            },
            onDelete: function(obj) {
                db.unRegisterInReverseMap('directory', obj);
                Object.keys(db.directory).forEach(x => {
                    var item = db.directory[x];
                    var index = item.next.indexOf(obj._id);
                    if (index == -1) {
                        return;
                    }
                    item.next.splice(index, 1);
                })
            }
        }),
        tag: _handler({
            onMerge: function(obj, prev) {
                db.unRegisterInReverseMap('tag', prev);
                db.registerInReverseMap('tag', obj);
            },
            onCreate: function(obj) {
                db.registerInReverseMap('tag', obj);
            },
            onDelete: function(obj) {
                db.unRegisterInReverseMap('tag', obj);
            }
        }),
        image: _handler({
            onCreate: function(obj) {
                db.createMapItem(obj);
            },
            onDelete: function(obj) {
                if (!(obj._id in db.reverseMap)) {
                    console.warn('id unmake:', obj._id, '\n\tis there something wrong?');
                    return;
                }
                var {directory, tag} = db.reverseMap[obj._id];
                Object.keys(directory)
                .map(_id => {
                    if (db.directory[_id]) {
                        console.warn('forget to unRegisterInReverseMap? \n\ttype is directory, id:', _id);
                        return _id;
                    } else {
                        db.unRegisterInReverseMap('directory', {
                            _id, content: [obj._id]
                        })
                        return null;
                    }
                })
                .filter(x => x)
                .forEach(_id => {
                    var index = db.directory[_id].content.indexOf(obj._id);
                    if (index == -1) {
                        console.warn('reverseMap has something wrong, is db.directory.content updated ircorrect?');
                        return;
                    }
                    db.directory.splice(index, 1);
                });
                Object.keys(tag)
                .map(_id => {
                    if (db.tag[_id]) {
                        console.warn('forget to unRegisterInReverseMap? \n\ttype is tag, id:', _id);
                        return _id;
                    } else {
                        db.unRegisterInReverseMap('tag', {
                            _id, content: [obj._id]
                        })
                        return null;
                    }
                })
                .filter(x => x)
                .forEach(_id => {
                    var index = db.tag[_id].content.indexOf(obj._id);
                    if (index == -1) {
                        console.warn('reverseMap has something wrong, is db.tag.content updated ircorrect?');
                        return;
                    }
                    db.tag.splice(index, 1);
                });
                delete reverseMap[obj._id];
            }
        })
    },
    data: {
        reverseMap: {},
        image: {},
        directory: {},
        tag: {},
        getDB() {
            return db;
        },
        _oid: 0
    }
};

Object.defineProperties(db, {
  'image': {
    get() {
        return this.data.image;
    }
  },
  'directory': {
    get() {
        return this.data.directory;
    }
  },
  'tag': {
    get() {
        return this.data.tag;
    }
  },
  // etc. etc.
});

void function() {
function renameHandler(modelName, url) {
    return function(_id, name) {
        var model = db[modelName];
        if (model && !model.hasOwnProperty(_id) || !name) {
            return $.Deferred(w => w.reject(null));
        }
        return $.post(url, {
            _id,
            args: JSON.stringify({
                name: name
            })
        }).then(
            res => {
                return db.ajax.defaultResCreator(res.self, 'merge');
            }            
        ).then(
            db.ajax.defaultHandler.bind(db.ajax, model)
        )
    }
}

db.ajax.renameImage = renameHandler('image', '/model/image/rename');
db.ajax.renameDirectory = renameHandler('directory', '/model/directory/rename');
db.ajax.renameTag = renameHandler('tag', '/model/tag/rename');

}();


void function() {
db.ajax.addExtendInfo = function(_id, obj) {
    return $.post('/model/image/addExtendInfo', {
        _id,
        args: JSON.stringify({
            info: obj
        })
    })
    .then(res => {
        return db.ajax.defaultResCreator(res.self, 'merge');
    })
    .then(db.ajax.defaultHandler.bind(db.ajax, db.image));
}

db.ajax.updateExtendInfo = function(_id, info) {
    if (!db.image.hasOwnProperty(_id)
        || !db.image[_id].extendInfo.filter(x => x._id == info._id).length
        || !info.content) {
        return $.Deferred(w => w.reject(null));
    }
    return $.post('/model/image/updateExtendInfo', {
        _id,
        args: JSON.stringify({
            info
        })
    }).then(
        res => {
            return db.ajax.defaultResCreator(res.self, 'merge');
        }            
    ).then(
        db.ajax.defaultHandler.bind(db.ajax, db.image)
    )
}

db.ajax.removeExtendInfo = function(_id, info) {
    if (!db.image.hasOwnProperty(_id)
        || !db.image[_id].extendInfo.filter(x => x._id == info._id).length) {
        return $.Deferred(w => w.reject(null));
    }
    return $.post('/model/image/removeExtendInfo', {
        _id,
        args: JSON.stringify({
            info
        })
    }).then(
        res => {
            return db.ajax.defaultResCreator(res.self, 'merge');
        }            
    ).then(
        db.ajax.defaultHandler.bind(db.ajax, db.image)
    )
}
}();

function _handler(methods) {
    return Object.assign({}, _handler.prototype,  methods);
}

_handler.prototype = {
    onCreate: function(obj) {
        console.log('onCreate', this, obj);
    },
    onUpdate: function(obj, prev) {
        console.log('onUpdate', this, obj, prev);
    },
    onDelete: function(obj) {
        console.log('onDelete', this, obj);
    },
    onMerge: function(obj, initial, cur) {
        console.log('onMerge', this, obj, cur);
    }
       
}

export default db;